package jmine.tec.web.wicket.pages.form;

import static jmine.tec.report.impl.table.RendererParameterInterface.PARAM_HIDDEN;
import static jmine.tec.web.wicket.pages.form.WebFormPagesMessages.ERROR_CANNOT_FIND_ENTITY_TO_VIEW;
import static jmine.tec.web.wicket.pages.form.WebFormPagesMessages.LABEL_SEARCH_TITLE;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import jmine.tec.persist.PersistMessages;
import jmine.tec.report.impl.ReportBuilder;
import jmine.tec.report.impl.table.ReportTableBuilder;
import jmine.tec.utils.reflection.GenericTypeResolverImpl;
import jmine.tec.web.wicket.component.command.TableCommandsProvider;
import jmine.tec.web.wicket.component.command.button.ButtonCommand;
import jmine.tec.web.wicket.component.command.button.ButtonCommandsPanel;
import jmine.tec.web.wicket.component.command.button.SearchCommand;
import jmine.tec.web.wicket.component.command.button.SearchCommandHandler;
import jmine.tec.web.wicket.component.command.img.ImageCommand;
import jmine.tec.web.wicket.component.table.SearchResultTable;
import jmine.tec.web.wicket.spring.SpringBeanReference;

import org.apache.wicket.PageParameters;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.validation.IFormValidator;
import org.apache.wicket.markup.html.panel.FeedbackPanel;
import org.apache.wicket.markup.html.panel.Panel;
import org.apache.wicket.model.CompoundPropertyModel;

import bancosys.tec.persist.Persistable;
import bancosys.tec.persist.dao.BaseDAO;
import bancosys.tec.persist.dao.BaseDAOFactory;
import bancosys.tec.persist.dao.BeanNotFoundException;
import bancosys.tec.persist.exception.PersistenceException;

/**
 * Implementação base de uma página de listagem de entidades.
 * 
 * @author Rafael Volpato
 * @date Aug 8, 2011
 * @param <BO> Tipo da entidade exibida na tabela de listagem
 * @param <M> Tipo do model do formulario
 */
public abstract class ListPage<M, BO extends Persistable> extends AbstractFormPage implements SearchCommandHandler<BO>,
        TableCommandsProvider {

    protected final SpringBeanReference<BaseDAOFactory> daoFactoryRef = SpringBeanReference.forName("daoFactory");

    private Form<M> form;

    /**
     * Construtor.
     */
    public ListPage() {
        super();
    }

    /**
     * Construtor.
     * 
     * @param pageParameters {@link PageParameters}
     */
    public ListPage(PageParameters pageParameters) {
        super(pageParameters);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected void onInitialize() {
        super.onInitialize();
        this.generateComponents();        
    }
    
    /**
     * Gera os principais componentes da página.
     */
    private void generateComponents() {
        this.form = new Form<M>("mainForm");
        FeedbackPanel feedbackPanel = new FeedbackPanel("feedback");
        feedbackPanel.setOutputMarkupId(true);
        this.add(feedbackPanel);

        M model = this.createModel();
        if (model != null) {
            this.form.setModel(new CompoundPropertyModel<M>(model));
        }

        this.form.add(new ButtonCommandsPanel("pageCommands", this.getPageCommands()));

        this.form.add(new Label("searchTitle", LABEL_SEARCH_TITLE.create(this.getPageName()).getMessage()));
        this.addSearchFields(this.form);

        this.generateTable();
        
        for (IFormValidator validator : this.createFormValidators()) {
            this.form.add(validator);
        }
        this.add(this.form);

    }

    /**
     * Generates the report table that will represent all results
     */
    protected void generateTable() {
        ReportBuilder reportBuilder = new ReportBuilder(this.getFullPageName(), this.getResultFileName());
        ReportTableBuilder<BO> tableBuilder = reportBuilder.createTableBuilder(this.getEntityClass(), "pesquisa");
        this.addResultTableColumns(tableBuilder);
        tableBuilder.addNumberColumn("pk", "", "pk", 0).getParameters().put(PARAM_HIDDEN, "true");
        tableBuilder.close();

        Panel searchResultPanel = this.createSearchResultPanel("pesquisa", reportBuilder);
        searchResultPanel.setOutputMarkupId(true);
        searchResultPanel.setVisible(true);
        this.form.addOrReplace(searchResultPanel);
    }

    /**
     * Cria um painel que vai ser responsável por renderizar o resultado da pesquisa.
     * 
     * @param id id
     * @param reportBuilder {@link ReportBuilder}
     * @return {@link Panel}
     */
    protected Panel createSearchResultPanel(String id, ReportBuilder reportBuilder) {
        return new SearchResultTable<BO>(id, reportBuilder.createReport(), this.getEntityClass(), this, this);
    }

    /**
     * Cria uma nova instancia do model do formulario.
     * 
     * @return uma nova instancia do model
     */
    protected abstract M createModel();

    /**
     * Retorna a classe da entidade que esta sendo manipulada por esta página.
     * 
     * @return a classe da entidade que esta sendo manipulada por esta página
     */
    @SuppressWarnings("unchecked")
    public Class<BO> getEntityClass() {
        try {
            return (Class<BO>) GenericTypeResolverImpl.getInstance().resolveTypeForParameterizedType(
                    (ParameterizedType) this.getClass().getGenericSuperclass(), this.getClass(), 1);
        } catch (IllegalArgumentException e) {
            throw new PersistenceException(PersistMessages.INVALID_BEAN_IMPLEMENTATION.create(this.getClass()), e);
        }
    }

    /**
     * Carrega a entidade do banco de dados.
     * 
     * @param entityPK pk da entidade
     * @return entidade de PK informada
     * @throws BeanNotFoundException se não existir nenhuma entidade com o PK informado no banco de dados
     */
    protected final BO loadEntity(Serializable entityPK) throws BeanNotFoundException {
        BaseDAO<BO> dao = this.daoFactoryRef.get().getGenericDAO(this.getEntityClass());
        return dao.findByPk(entityPK);
    }

    /**
     * Retorna o model associada ao formulario.
     * 
     * @return o model associada ao formulario
     */
    protected final M getModel() {
        return this.form.getModelObject();
    }

    /**
     * Retorna a lista de comandos que deve ser exibidos junto com a tabela de entidade encontradas.
     * 
     * @return lista de comandos que devem ser exibidos
     */
    protected List<ButtonCommand> getPageCommands() {
        List<ButtonCommand> commands = new ArrayList<ButtonCommand>();
        commands.add(new SearchCommand());
        return commands;
    }

    /**
     * Informa se deve ser incluida na tabela de resultados uma coluna de ações.
     * 
     * @return <code>true</code> se deve ser incluida na tabela de resultados uma coluna de ações, <code>false</code> caso contrário
     */
    public boolean shouldIncludeTableCommands() {
        return false;
    }

    /**
     * Retorna a lista de comandos que deve ser exibidos junto com a tabela de entidade encontradas.
     * 
     * @param entityPK entityPK
     * @return lista de comandos que devem ser exibidos
     */
    public final List<ImageCommand> getTableCommands(Long entityPK) {
        try {
            return this.getTableCommands(this.loadEntity(entityPK));
        } catch (BeanNotFoundException e) {
            this.error(ERROR_CANNOT_FIND_ENTITY_TO_VIEW.create(this.getPageName(), entityPK));
            return Collections.emptyList();
        }
    }

    /**
     * Retorna a lista de comandos que deve ser exibidos junto com a tabela de entidade encontradas.
     * 
     * @param entity entity
     * @return lista de comandos que devem ser exibidos
     */
    protected List<ImageCommand> getTableCommands(BO entity) {
        return new ArrayList<ImageCommand>();
    }

    /**
     * Retorna o nome de arquivo padrão que deve ser utilizado para salvar os arquivos exportados por essa página.
     * 
     * @return O nome de arquivo padrão que deve ser utilizado para salvar os arquivos exportados por essa página
     */
    protected String getResultFileName() {
        return "Pesquisa " + this.getPageName();
    }

    /**
     * {@inheritDoc}
     */
    public void addReportHeader(ReportBuilder reportBuilder) {
        // hook
    }

    /**
     * Validators to be added in this page form.
     * 
     * @return a list of IFormValidator
     */
    protected List<IFormValidator> createFormValidators() {
        return Collections.emptyList();
    }
    
    /**
     * Inclui os campos de pesquisa no container
     * 
     * @param container container
     */
    protected abstract void addSearchFields(WebMarkupContainer container);

    /**
     * Inclui as colunas que devem ser exibidas na tabela de resultado.
     * 
     * @param table tabela
     */
    protected abstract void addResultTableColumns(ReportTableBuilder<BO> table);
    
}
